interface FnType {
  (...args: any[]): any
}

/**
 * 函数柯里化辅助生曾
 * @param {Function} fn 需要柯里化的函数
 * @param {...*} args 实参
 * @returns 一个新的`fn`函数
 */
const generateCurryFn = function (fn: FnType, ...args: any[]): FnType {
  return function (this: any, ...innerArgs: any[]) {
    return fn.apply(this, args.concat(innerArgs))
  }
}

/**
 * @description 函数柯里化
 * @param {Function} fn 源函数
 * @param {number|undefined} length 可函数参数个数, default: `fn`形参个数
 * @returns 一个新的函数或`fn`调用的结果
 * @example
 * -------------@1---------------
 * const fn = (a, b, c) => a + b - c
 * const curryFn = loopCurry(fn)
 * // => curryFn(30)(40)(20) --> 50
 * -------------@2---------------
 * const curryFn = loopCurry(fn)
 * // => curryFn(30, 40)(20) --> 50
 * -------------@3---------------
 * const curryFn = loopCurry(fn)
 * // => curryFn(30)(40, 20) --> 50
 * -------------@1举例分析---------------
 * fn = (a, b, c) => a + b - c => length = 3
 * -
 * step: first
 * params: 30 => arguments.length = 1. 1 < 3 => combined = [fn, 30] ==> loopCurry(generateCurryFn.apply(this, [fn, 30]), 3-1 = 2)
 * generateCurryFn.apply(this, [fn, 30]) => 返回函数f1: f1 = () => fn.apply(this, [30].concat(Array.prototype.slice.call(arguments)))
 * -
 * step: second. loopCurry(f1, 3-1 = 2)
 * length = 2
 * params: 40 => arguments.length = 1. 1 < 2 => combined = [f1, 40] ==> loopCurry(generateCurryFn.apply(this, [f1, 40]), 2 - 1 = 1)
 * generateCurryFn.apply(this, [f1, 40]) 返回函数f2: f2 = () => f1.apply(this, [40].concat(Array.prototype.slice.call(arguments)))
 * -
 * step: third. loopCurry(f2, 2 - 1 = 1)
 * length = 1
 * params: 20 => arguments.length = 1. 1 === 1 => f2(20)
 * -
 * step: forth
 * f2(20) 执行 (20) => f1.apply(this, [40].concat([20])) => f1.apply(this, [40, 20])) => 执行 ([40, 20]) => fn.apply(this. [30].concat([40, 20])) => fn.apply(this, [30, 40, 20])
 */
export const loopCurry = function (fn: FnType, length?: number): FnType | ReturnType<FnType> {
  length = length || fn.length
  return function (this: any, ...args: any[]) {
    if (args.length < length) {
      const combined = [fn].concat(args)
      return loopCurry(generateCurryFn.apply(this, combined as any), length - args.length)
    }
    return fn.apply(this, args)
  }
}

/**
 * @deprecated
 * @description 函数柯里化 - 建议使用 `loopCurry`
 * @param {Function} fn 源函数
 * @param {...* } outerArgs fn函数的参数
 * @returns 一个新的函数或`fn`调用的结果
 * @example
 * -----------------------
 * const fn = (a, b, c) => a + b + c
 * const curryFn = curry(fn, 1)
 * // => curryFn(2)(3) ---> 6
 * ------------------------------
 * const fn = (a, b, c) => a + b - c
 * const curryFn = curry(fn)
 * // => curryFn(3)(4)(2) ---> 5
 */
export const curry = (fn: FnType, ...outerArgs: any[]): FnType | ReturnType<FnType> => {
  const { length } = fn

  return function (this: any, ...innerArgs: any[]) {
    const curryArgs = outerArgs.slice(0)
    for (let i = 0; i < innerArgs.length; i++) {
      curryArgs.push(innerArgs[i])
    }
    if (curryArgs.length < length) {
      return curry.call(this, fn, ...curryArgs)
    }
    return fn.apply(this, curryArgs)
  }
}
